import pandas as pd
import time
import datetime

df = pd.read_csv('demand_profile.csv')
print(df.head())  # head( )函数默认只能读取前五行数据，df.head(10)


def apply_tariff(kwh, hour):
    """计算每个小时的电费"""
    if 0 <= hour < 7:
        rate = 12
    elif 7 <= hour < 17:
        rate = 20
    elif 17 <= hour < 24:
        rate = 28
    else:
        raise ValueError(f'Invalid hour: {hour}')
    return rate * kwh


# 装饰器其实就是一个返回一个函数的函数，就是重新了函数
def timeit0(func):
    def result():
        start_time = time.time()
        func()
        end_time = time.time()
        print('函数运行时间为：%.2fs' % (end_time - start_time))

    return result


def timeit(out='函数运行时间为：%.2fs'):
    def decorator(func):
        def result(*args):
            start_time = time.time()
            func(*args)
            end_time = time.time()
            print(out % (end_time - start_time))  # out为参数
            # print('Best of '+ repeat + 'trials with '+ number + ' function calls per trial:Function `apply_tariff_loop` ran in average of %.2f sseconds.' % (end_time - start_time))  # out为参数

        return result

    return decorator


# import datetime
# str_p = '2019-01-30 15:29:08'
# dateTime_p = datetime.datetime.strptime(str_p,'%Y-%m-%d %H:%M:%S')
# print(dateTime_p) # 2019-01-30 15:29:08

@timeit(out='函数运行时间为：%.2fs')  # 装饰器其实就是一个返回一个函数的函数，就是重新了函数
def apply_tariff_loop(df):
    """用for循环计算enery cost，并添加到列表"""
    energy_cost_list = []
    for i in range(len(df)):
        # 获取用电量和时间（小时）
        energy_used = df.iloc[i]['energy_kwh']
        date_time = df.iloc[i]['date_time']
        times = datetime.datetime.strptime(date_time, "%d-%m-%Y %H:%M")
        hour = times.hour
        energy_cost = apply_tariff(energy_used, hour)
        energy_cost_list.append(energy_cost)
    df['cost_cents'] = energy_cost_list


apply_tariff_loop(df)


################## 使用 iterrows循环 ################
# 可以通过pandas引入iterrows方法让效率更高。这些都是一次产生一行的生成器方法，类似scrapy中使用的yield用法。
# .iterrows为DataFrame中的每一行产生（index，series）这样的元组。
# .itertuples为每一行产生一个namedtuple，并且行的索引值作为元组的第一个元素。
# nametuple是Python的collections模块中的一种数据结构，其行为类似于Python元组，但具有可通过属性查找访问的字段。
@timeit(out='函数运行时间为：%.2fs')
def apply_tariff_iterrows(df):
    energy_cost_list = []
    for index, row in df.iterrows():
        # 获取用电量和时间（小时）
        energy_used = row['energy_kwh']
        # hour = row['date_time'].hour
        date_time = row['date_time']
        times = datetime.datetime.strptime(date_time, "%d-%m-%Y %H:%M")
        hour = times.hour
        # 添加cost列表
        energy_cost = apply_tariff(energy_used, hour)
        energy_cost_list.append(energy_cost)
    df['cost_cents'] = energy_cost_list


apply_tariff_iterrows(df)


################## pandas的apply方法 ################
# 可以使用.apply方法而不是.iterrows进一步改进此操作。
# pandas的.apply方法接受函数callables并沿DataFrame的轴(所有行或所有列)应用。
# 下面代码中，lambda函数将两列数据传递给apply_tariff()
# apply的语法优点很明显，行数少，代码可读性高。在这种情况下，所花费的时间大约是iterrows方法的一半。
# 但是，这还不是“非常快”。一个原因是apply()将在内部尝试循环遍历Cython迭代器。
# 但是在这种情况下，传递的lambda不是可以在Cython中处理的东西，因此它在Python中调用并不是那么快。
@timeit(out='函数运行时间为：%.2fs')
def apply_tariff_withapply(df):
    df['cost_cents'] = df.apply(
        lambda row: apply_tariff(
            kwh=row['energy_kwh'],
            hour=datetime.datetime.strptime(row['date_time'], "%d-%m-%Y %H:%M").hour),
        axis=1)


apply_tariff_withapply(df)

##################矢量化操作：使用.isin选择数据##############
# 如果你不基于一些条件，而是可以在一行代码中将所有电力消耗数据应用于该价格：df ['energy_kwh'] * 28，类似这种。
# 那么这个特定的操作就是矢量化操作的一个例子，它是在pandas中执行的最快方法。
# 根据你的条件，选择和分组DataFrame，然后对每个选定的组应用矢量化操作。
# 使用pandas的.isin()方法选择行，然后在矢量化操作中实现新特征的添加。在执行此操作之前，如果将date_time列设置为DataFrame的索引，会更方便
# 布尔值标识了DataFrame索引datetimes是否落在了指定的小时范围内。然后把这些布尔数组传递给DataFrame的.loc，将获得一个与这些小时匹配的DataFrame切片。然后再将切片乘以适当的费率，这就是一种快速的矢量化操作了。
# 将date_time列设置为DataFrame的索引
df.set_index('date_time', inplace=True)


@timeit(out='函数运行时间为：%.2fs')
def apply_tariff_isin(df):
    # times = datetime.datetime.strptime(df.index, "%d-%m-%Y %H:%M")
    # 定义小时范围Boolean数组
    peak_hours = df.index.hour.isin(range(17, 24))
    shoulder_hours = df.index.hour.isin(range(7, 17))
    off_peak_hours = df.index.hour.isin(range(0, 7))

    # 使用上面apply_traffic函数中的定义
    df.loc[peak_hours, 'cost_cents'] = df.loc[peak_hours, 'energy_kwh'] * 28
    df.loc[shoulder_hours, 'cost_cents'] = df.loc[shoulder_hours, 'energy_kwh'] * 20
    df.loc[off_peak_hours, 'cost_cents'] = df.loc[off_peak_hours, 'energy_kwh'] * 12


# apply_tariff_isin(df)


##################矢量化切片操作：使用.cut函数来自动完成切割#################
# 在上面apply_tariff_isin中，我们通过调用df.loc和df.index.hour.isin三次来进行一些手动调整。
# 如果我们有更精细的时间范围，你可能会说这个解决方案是不可扩展的。
# 但在这种情况下，我们可以使用pandas的pd.cut()函数来自动完成切割
# pd.cut()会根据bin列表应用分组。
# 其中include_lowest参数表示第一个间隔是否应该是包含左边的。
# 这是一种完全矢量化的方法，它在时间方面是最快的
@timeit(out='函数运行时间为：%.2fs')
def apply_tariff_cut(df):
    cents_per_kwh = pd.cut(x=df.index.hour,
                           bins=[0, 7, 17, 24],
                           include_lowest=True,
                           labels=[12, 20, 28]).astype(int)
    df['cost_cents'] = cents_per_kwh * df['energy_kwh']


apply_tariff_cut(df)
